
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextPane;

public class Distributor implements Runnable {

    HashMap<String, ComunicationUniCast> comunicaçãoComOsProcessos;
    ArrayList<String> nomeDosProcessos;
    JTextPane text;
    int ciclo, fase, inicio;
    String cor;
    ServerComunication server;
    ServerComunication serverExt;
    private boolean passo, ri;
    String mensagem;
    HashMap<String, String> mensagensComOsProcessos;

    Distributor(JTextPane jTextPane1, int inicio, int porta, HashMap<String, ComunicationUniCast> aux, ArrayList<String> processos) {
        text = jTextPane1;
        ciclo = 3;
        fase = 0;
        cor = "vermelho";
        this.inicio = inicio;
        comunicaçãoComOsProcessos = aux;
        nomeDosProcessos = processos;
        mensagensComOsProcessos = new HashMap<String, String>();
        server = new ServerComunication(this.text, porta);
        serverExt = new ServerComunication(this.text, 8010);
    }

    // metodo que libera a ação da instancia
    public void passar() {
        passo = true;
    }

    public void escreve(String s) {
        if (!(text == null)) {
            text.setText((!(text.getText().isEmpty()) ? text.getText() : "\n") + s + "\n");
        } else {
            System.out.println("nodo: " + s);
        }
    }

    public void run() {
        comecaServidor();
        escreve("Clique em passo depois que todos os processos e nodos estiverem abertos.");
        passo = false;
        while (!passo) {
            System.out.print("");
        }
        escreve("Cliquei. E vou me conectar aos clientes");
        comecaCliente();
        passo = false;
        escreve("Esperando Clique para começar o algoritmo.");
        while (!passo) {
            System.out.print("");
        }
        escreve("Comecei.");
        while (true) {
            //iniciador
            escreve("------------------------------------------------------------------------");
            if (inicio == 0) {
                //envia o token para o proximo no ciclo
                escreve("Enviando Externo a mensagem: " + "nodo:TOKEN:" + fase);
                espera(500);
                enviaExterno("nodo:TOKEN:" + fase);
                cor = "azul";
                inicio = 1;
                escreve("Fase: " + fase + "\t Cor: " + cor);
            } //outros
            else {
                espera(3000);
                //recebe o pacote do antecessor no ciclo
                mensagem = recebeExterno();
                escreve("Recebi a mensagem do externo: " + mensagem);
                espera(450);
                //se for token
                if (isToken()) {
                    fase = fasedotoken();
                    escreve("A fase que está é:" + fase);
                    // se a fase for igual ao ciclo - terminou a computação
                    if (fase == ciclo) {
                        escreve("Acabou Computação");
                        enviaExterno(mensagem);
                    } // se a minha cor estiver em vermelho - mando para os processos e espero resposta e então me pinto de azul, atualizo minha fase para 0 e mando o token para o seguinte
                    else if (cor.equalsIgnoreCase("vermelho")) {
                        espera(250);
                        enviaInterno(mensagem + ":vermelho");
                        espera(350);
                        escreve("Enviei internamente a mensagem: " + mensagem + ":vermelho");

                        auxilioProc();

                        ri = false;
                        while (!ri) {
                            escreve("Ainda não recebi dos processos");
                            espera(400);
                            if (recebeInterno()) {
                                ri = true;
                                espera(200);
                                escreve("Recebi dos processos");
                            }
                        }
                        if (checaIntegridade()) {
                            cor = "azul";
                            fase = fasedotoken();
                            espera(200);
                            enviaExterno("nodo:"+mensagem);
                            espera(100);
                            escreve("Mandei a mensagem: " + mensagem);
                            escreve("A fase que está é:" + fase);
                        } else {
                            escreve("Falha na integridade dos pacotes recebido");
                        }
                    } // se minha cor for azul - passsa para os processos espero a resposta e atualizo a minha fase e passo para o proximo nodo
                    else if (cor.equalsIgnoreCase("azul")) {
                        espera(100);
                        enviaInterno(mensagem + ":azul");
                        espera(200);

                        auxilioProc();

                        ri = false;
                        while (!ri) {
                            escreve("Ainda não recebi dos processos");
                            espera(200);
                            if (recebeInterno()) {
                                ri = true;
                                espera(400);
                                escreve("Recebi dos processos");
                            }
                        }
                        if (checaIntegridade()) {
                            fase = fasedotoken();
                            espera(120);
                            enviaExterno("nodo:"+mensagem);
                            espera(400);
                            escreve("Mandei a mensagem: " + mensagem);
                            escreve("----------------------------------------------------------------");
                            escreve("Fase: " + fase + "\t Cor: " + cor);

                        } else {
                            escreve("Falha na integridade dos pacotes recebido");
                        }
                    }
                } else {
                    escreve("Recebi uma mensagem que não é um token: " + mensagem);
                    enviaInterno(mensagem);
                    cor = "vermelho";
                }
            }
        }
    }

    private void enviaInterno(String t) {
        //envia a mensagem que recebeu para todos os processos que o distribuidor é responsavel
        ComunicationUniCast aux;
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase("prox")) {
                aux = comunicaçãoComOsProcessos.get(s);
                if (aux == null) {
                    escreve("problema envio interno");
                }
                aux.envia(t);
                espera(120);
            }
        }
    }

    private boolean recebeInterno() {
        //recebe o pacote de todos os processos que ele é responsavel
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase("prox")) {
                escreve("Dentro do recebe " + s);
                ComunicationUniCast aux = comunicaçãoComOsProcessos.get(s);
                espera(150);
                String aux1 = aux.recebe();
                espera(150);
                escreve("Recebi a mensagem do " + s + ": " + aux1);
                mensagensComOsProcessos.put(s, aux1);
            }
        }

        return true;
    }

    private void auxilioProc() {
        String auxilio = "";
        HashMap<String, String> auxilioMensagens = new HashMap<String, String>();

        escreve("Receber os pacotes para auxiliar.");
        //recebe o pacote
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase("prox")) {
                //escreve("Auxiliando " + s);
                ComunicationUniCast aux = comunicaçãoComOsProcessos.get(s);
                espera(150);
                String aux1 = aux.recebe();
                espera(150);
                //escreve("Recebi a mensagem do " + s + ": " + aux1);
                auxilioMensagens.put(s, aux1);
            }
        }

        //escreve("Montar o pacote auxilio.");
        for (String s: auxilioMensagens.keySet()){

            auxilio = auxilio + auxilioMensagens.get(s) + "_";

        }
        //escreve(auxilio);
        //encaminha para os outros
        enviaInterno(auxilio);

        //--------------------------------------------------------------
        auxilioMensagens = new HashMap<String, String>();
        auxilio = "";
        //escreve("Receber os pacotes para auxiliar.");
        //recebe o pacote
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase("prox")) {
                //escreve("Auxiliando " + s);
                ComunicationUniCast aux = comunicaçãoComOsProcessos.get(s);
                espera(150);
                String aux1 = aux.recebe();
                espera(150);
                //escreve("Recebi a mensagem do " + s + ": " + aux1);
                auxilioMensagens.put(s, s+":"+aux1);
            }
        }

        //escreve("Montar o pacote auxilio.");
        for (String s: auxilioMensagens.keySet()){

            auxilio = auxilio + auxilioMensagens.get(s) + "_";

        }
        //escreve(auxilio);
        //encaminha para os outros
        enviaInterno(auxilio);

    }

    private void enviaExterno(String s) {
        // envia o pacote para o proximo no ciclo do algoritmo de detecção
        ComunicationUniCast aux;
        aux = comunicaçãoComOsProcessos.get("prox");
        if (aux == null) {
            escreve("Problema no envio externo");
        }
        aux.envia(s);
        espera(250);
    }

    private String recebeExterno() {
        //recebe o pacote do antecessor dele no ciclo do grafo
        String aux = null;
        while (aux == null) {
            try {
                espera(200);
                aux = serverExt.recebe();
                espera(200);
                escreve("ainda não recebi do externo.");
            } catch (IOException ex) {
                escreve("Problema em receber o pacote do servidor.");
            }
        }
        return aux;
    }

    private boolean isToken() {
        //<IDENTIFICADOR>:<TIPO DE PACOTE>:<DADOS>
        //verifica se o pacote é um token
        String[] desmembro = mensagem.split(":");
        if (desmembro[1].contains("TOKEN")) {
            return true;
        } else {
            return false;
        }
    }

    private int fasedotoken() {
        //formato do pacote:<IDENTIFICADOR>:<TIPO DE PACOTE>:<DADOS>
        //retorna a fase que esta o algoritmo, valor retirado da mensagem do token
        String aux = "";
        String[] desmembro = mensagem.split(":");
        if (desmembro[1].equals("TOKEN"))
        aux = desmembro[2];
        else
            aux = desmembro[1];

        int r = Integer.parseInt(aux);

        return r;
    }

    private boolean checaIntegridade() {
        //se todos os pacotes forem iguais
        String aux = nomeDosProcessos.get(1);
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase("prox")) {
                if (!(mensagensComOsProcessos.get(aux).equalsIgnoreCase(mensagensComOsProcessos.get(s)))) {
                    return false;
                }
            }
        }
        mensagem = mensagensComOsProcessos.get(aux);
        return true;
    }

    // --------- Parte da comunicação-------------
    private void comecaServidor() {
        new Thread(server).start();
        espera(1000);
        new Thread(serverExt).start();
        espera(1000);
    }

    private void comecaCliente() {
        ComunicationUniCast aux;
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo")) {
                aux = comunicaçãoComOsProcessos.get(s);
                new Thread(aux).start();
                espera(1000);

            }
        }
    }

    private void espera(int i) {
        try {
            Thread.sleep(i);
        } catch (InterruptedException ex) {
            escreve("Problema ao esperar.");
        }
    }
}
